package com.myproject.mall.user.service.impl;

import cn.hutool.crypto.SmUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.myproject.mall.common.constants.GlobalCode;
import com.myproject.mall.common.constants.JWTConstants;
import com.myproject.mall.common.constants.RedisConstants;
import com.myproject.mall.common.exception.GlobalException;
import com.myproject.mall.common.util.BloomFilter;
import com.myproject.mall.common.util.RegexUtil;
import com.myproject.mall.domain.dto.PasswordForm;
import com.myproject.mall.domain.dto.RegistrationFormDTO;
import com.myproject.mall.domain.entity.Follow;
import com.myproject.mall.domain.entity.User;
import com.myproject.mall.domain.enums.UserRole;
import com.myproject.mall.domain.holder.UserHolder;
import com.myproject.mall.domain.vo.ShopVO;
import com.myproject.mall.domain.vo.UserVO;
import com.myproject.mall.user.client.ShopServiceClient;
import com.myproject.mall.user.mapper.FollowMapper;
import com.myproject.mall.user.mapper.UserMapper;
import com.myproject.mall.user.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private FollowMapper followMapper;
    @Autowired
    private ShopServiceClient shopClient;
    @Autowired
    private BloomFilter bloomFilter;


    private String generateCode(String phone)
    {
        if(redisTemplate.opsForValue().get(RedisConstants.USER_CODE_PREFIX + phone)!= null)
        {
            throw new GlobalException("5分钟后再尝试");
        }

//        产生6为随机验证码
        String code = String.format("%06d", (int)(Math.random() * 1000000));
        log.debug("验证码：{}", code);

//      这是缓存验证码，缓存5分钟
        redisTemplate.opsForValue().set(RedisConstants.USER_CODE_PREFIX + phone, code, RedisConstants.USER_CODE_EXPIRE, TimeUnit.MINUTES);

        return code;
    }
    @Override
    public String sendCode(String phone) {
        if(RegexUtil.isPhoneInvalid( phone))
        {
            throw new GlobalException("手机号错误");
        }

        String code = generateCode(phone);
//        这里是向手机发送验证码的代码

        return code;
    }

    @Override
    public UserVO loginByPassword(String phone, String password) {

        if(RegexUtil.isPhoneInvalid( phone))
        {
            throw new GlobalException("手机号错误");
        }
        if(RegexUtil.isPasswordInvalid( password)) throw new GlobalException("密码错误");


        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        if(user == null) {
            throw new GlobalException(GlobalCode.USER_NOT_FOUND);
        }

        String cPassword = SmUtil.sm3(password);
        if(!cPassword.equals(user.getPassword())) {
            throw new GlobalException("密码错误");
        }
        return new UserVO(user);
    }

    @Override
    public UserVO loginByCode(String phone, String code) {

        if(RegexUtil.isPhoneInvalid( phone))
        {
            throw new GlobalException("手机号错误");
        }
        if(RegexUtil.isCodeInvalid( code)) throw new GlobalException("验证码错误");

        String redisCode = redisTemplate.opsForValue().get(RedisConstants.USER_CODE_PREFIX + phone);
        if(redisCode == null) throw new GlobalException("验证码已过期，请重新发送");
        if(!redisCode.equals(code)) throw new GlobalException("验证码错误");

        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        if(user == null) {
            throw new GlobalException(GlobalCode.USER_NOT_FOUND);
        }
        return new UserVO(user);
    }

    @Override
    public User findUserById(String id) {
        return userMapper.selectById(id);
    }

    @Override
    public UserVO register(RegistrationFormDTO form) {
        if(RegexUtil.isPhoneInvalid( form.getPhone())) throw new GlobalException("手机号格式错误");
        if(RegexUtil.isPasswordInvalid( form.getPassword())) throw new GlobalException("密码格式错误");
        if(!form.getPassword().equals(form.getDuplicatePassword())) throw new GlobalException("密码不一致");

        if(bloomFilter.contains(form.getPhone())) {
            User isExit = userMapper.selectOne(new LambdaQueryWrapper<User>()
                    .eq(User::getPhone, form.getPhone()));
            if(isExit != null) throw new GlobalException("手机号已存在");
        }

        User uer = User.builder()
                .phone(form.getPhone())
                .password(SmUtil.sm3(form.getPassword()))
                .role(UserRole.ORDINARY)
                .username("SCMall_"+ form.getPhone())
                .build();
        userMapper.insert(uer);
        return new UserVO(uer);
    }

    @Override
    public void updateUserInfo(User user) {
        userMapper.updateById(user);
    }
    @Override
    public void updateUserPassword(PasswordForm passwordForm) {
        if(RegexUtil.isPhoneInvalid( passwordForm.getPhone())) throw new GlobalException("手机号格式错误");
        if(RegexUtil.isPasswordInvalid( passwordForm.getNewPassword())) throw new GlobalException("密码格式错误");
        if(passwordForm.getNewPassword().equals(passwordForm.getOldPassword())) throw new GlobalException("两次密码一致");

        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, passwordForm.getPhone()));

        if(user!= null && !SmUtil.sm3(passwordForm.getOldPassword()).equals(user.getPassword())) throw new GlobalException("密码错误");

        userMapper.update(new LambdaUpdateWrapper<User>()
                .eq(User::getPhone, passwordForm.getPhone())
                .set(User::getPassword, SmUtil.sm3(passwordForm.getNewPassword())));
    }

    @Override
    public String generateToken(String id, String username, String phone) {
        String SALT = "SCMall";

        String token = JWT.create()
                .withClaim("id", id)
                .withClaim("username", username)
                .withClaim("phone", phone)
                .withExpiresAt(new Date(System.currentTimeMillis() + JWTConstants.JWT_TOKEN_EXPIRE))
                .sign(Algorithm.HMAC256(SALT));
        Map<String, String> map = Map.of(
                "id", id,
                "username", username,
                "phone", phone
        );
        redisTemplate.opsForHash().putAll(RedisConstants.USER_TOKEN_PREFIX+token, map);
        redisTemplate.expire(RedisConstants.USER_TOKEN_PREFIX+token, RedisConstants.USER_TOKEN_EXPIRE, TimeUnit.MINUTES);

        return token;
    }

    @Override
    public UserVO getMe() {
        String id = UserHolder.getUser().getId();
        return new UserVO(userMapper.selectById(id));
    }

    @Transactional
    @Override
    public void follow(Long shopId, Boolean isFollow) {
        String userId = UserHolder.getUser().getId();
        String key = RedisConstants.USER_FOLLOW_PREFIX + userId;
        if(isFollow)
        {
            Follow follow = Follow.builder()
                    .userId(userId)
                    .shopId(shopId)
                    .build();
            int insert = followMapper.insert(follow);
            if(insert == 1) {
                redisTemplate.opsForSet().add(key, shopId.toString());
            }
        }
        else {
            int delete = followMapper.delete(new LambdaQueryWrapper<Follow>()
                    .eq(Follow::getShopId, shopId)
                    .eq(Follow::getUserId, userId));
            if(delete == 1) {
                redisTemplate.opsForSet().remove(key, shopId.toString());
            }
        }
    }

    @Override
    public List<ShopVO> getFollowList() {
        String userId = UserHolder.getUser().getId();
        Set<String> shopIds = redisTemplate.opsForSet().members(RedisConstants.USER_FOLLOW_PREFIX + userId);
        if(shopIds == null || shopIds.isEmpty()) return Collections.emptyList();
        List<Long> ids = shopIds.stream().map(Long::parseLong).toList();
        return shopClient.getShops(ids);
    }

    @Override
    public Boolean isFollow(Long shopId) {
        String userId = UserHolder.getUser().getId();
        return redisTemplate.opsForSet().isMember(RedisConstants.USER_FOLLOW_PREFIX + userId, shopId.toString());
    }
}
